const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const EslintWebpackPlugin = require('eslint-webpack-plugin');
// 将样式提取成单独的文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// css 压缩插件
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
// js 压缩插件
const TerserWebpackPlugin = require('terser-webpack-plugin');
// 图片压缩插件
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
// 将已存在的单个文件或整个目录复制到生成目录
const CopyPlugin = require('copy-webpack-plugin');
// 解析 vue
const { VueLoaderPlugin } = require('vue-loader');
const { DefinePlugin } = require('webpack');

// Element-plus 组件按需引入
const AutoImport = require('unplugin-auto-import/webpack');
const Components = require('unplugin-vue-components/webpack');
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers');

const isProduction = process.env.NODE_ENV === 'production';

// 处理样式资源公共方法
const getStyleLoaders = preProcessor => {
  return [
    isProduction ? MiniCssExtractPlugin.loader : 'vue-style-loader',
    'css-loader',
    {
      // 处理 css 兼容性问题
      // 配合 package.json 中的 browserslist 来指定兼容性
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins: ['postcss-preset-env'],
        },
      },
    },
    preProcessor && {
      loader: preProcessor,
      options:
        preProcessor === 'sass-loader'
          ? {
              additionalData: `@use "@/styles/element/index.scss" as *;`,
            }
          : {},
    },
  ].filter(Boolean);
};

// webpack 语法智能提示
/** @type {import('webpack').Configuration} */
module.exports = {
  entry: './src/main.js',
  output: {
    path: isProduction ? path.resolve(__dirname, '../dist') : undefined,
    filename: `static/js/[name]${isProduction ? '.[contenthash:10]' : ''}.js`,
    chunkFilename: `static/js/[name]${
      isProduction ? '.[contenthash:10]' : ''
    }.chunk.js`,
    assetModuleFilename: 'static/media/[hash:10][ext][query]',
    clean: true,
  },
  module: {
    rules: [
      // vue
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          // 开启缓存
          cacheDirectory: path.resolve(
            __dirname,
            'node_modules/.cache/vue-loader'
          ),
        },
      },
      // css
      {
        test: /\.css$/,
        use: getStyleLoaders(),
      },
      {
        test: /\.less$/,
        use: getStyleLoaders('less-loader'),
      },
      {
        test: /\.s[ac]ss$/,
        use: getStyleLoaders('sass-loader'),
      },
      {
        test: /\.styl$/,
        use: getStyleLoaders('stylus-loader'),
      },
      // 图片
      {
        test: /\.(jpe?g|png|gif|webp|svg)$/,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            // 图片小于 10kb 转换为 base64 格式
            maxSize: 10 * 1024,
          },
        },
      },
      // 字体资源
      {
        test: /\.(woff2?|tff)$/,
        /**
         * type: 'asset' 与 type: 'asset/resource' 的区别:
         *
         * - asset: 可以转为 base64
         * - asset/resource: 资源原封不动输出
         */
        type: 'asset/resource',
      },
      // js
      {
        test: /\.js$/,
        include: path.resolve(__dirname, '../src'),
        /**
         * 只有一个用 loader,
         * 多个 loader 用 use 数组
         */
        loader: 'babel-loader',
        options: {
          // 开启 babel 编译缓存
          cacheDirectory: true,
          // 缓存文件不压缩
          cacheCompression: false,
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      // 指定渲染模板：以 public/index.html 为模板创建文件
      // 新的html文件有两个特点：1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, '../public/index.html'),
    }),
    new EslintWebpackPlugin({
      // 处理的文件范围
      context: path.resolve(__dirname, '../src'),
      // 排除 node_modules，不作处理
      exclude: 'node_modules',
      // 使用缓存
      cache: true,
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        '../node_modules/.cache/.eslintcache'
      ),
    }),
    // 请确保引入这个插件！
    new VueLoaderPlugin(),
    // 解决页面警告
    // cross-env 定义的环境变量给打包工具使用
    // DefinePlugin 定义的环境变量给源码使用
    // 参考：https://link.vuejs.org/feature-flags
    new DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false,
    }),
    isProduction &&
      new MiniCssExtractPlugin({
        filename: 'static/css/[name].[contenthash:10].css',
        chunkFilename: 'static/css/[name].[contenthash:10].chunk.css',
      }),
    isProduction &&
      new CopyPlugin({
        patterns: [
          {
            // 将 public 目录复制到 dist 目录
            from: path.resolve(__dirname, '../public'),
            to: path.resolve(__dirname, '../dist'),
            toType: 'dir',
            noErrorOnMissing: true,
            globOptions: {
              // 复制时忽略 index.html
              ignore: ['**/index.html'],
            },
            info: {
              minimized: true,
            },
          },
        ],
      }),
    // 按需加载 Element-plus 组件
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [
        ElementPlusResolver({
          // 自定义主题，引入 sass
          importStyle: 'sass',
        }),
      ],
    }),
  ].filter(Boolean),
  mode: isProduction ? 'production' : 'development',
  devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
  optimization: {
    // 代码分割
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // layouts通常是admin项目的主体布局组件，所有路由组件都要使用的
        // 可以单独打包，从而复用
        // 如果项目中没有，请删除
        // layouts: {
        //   name: 'layouts',
        //   test: path.resolve(__dirname, '../src/layouts'),
        //   priority: 40,
        // },
        // 将vue相关的库单独打包，减少node_modules的chunk体积。
        vue: {
          test: /[\\/]node_modules[\\/]vue(.*)?[\\/]/,
          name: 'chunk-vue',
          chunks: 'initial',
          priority: 40,
        },
        // 如果项目中使用element-plus，此时将所有node_modules打包在一起，那么打包输出文件会比较大。
        // 所以我们将node_modules中比较大的模块单独打包，从而并行加载速度更好
        // 如果项目中没有，请删除
        elementPlus: {
          test: /[\\/]node_modules[\\/]element-plus[\\/]/,
          name: 'chunk-elementPlus',
          priority: 30,
        },
        libs: {
          test: /[\\/]node_modules[\\/]/,
          name: 'chunk-libs',
          chunks: 'initial',
          priority: 10, // 权重最低，优先考虑前面内容
        },
      },
    },
    runtimeChunk: {
      name: entrypoint => `runtime~${entrypoint.name}.js`,
    },
    // 指定是否需要压缩
    minimize: isProduction,
    // 压缩操作
    minimizer: [
      new CssMinimizerPlugin(),
      new TerserWebpackPlugin(),
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              [
                'svgo',
                {
                  plugins: [
                    'preset-default',
                    'prefixIds',
                    {
                      name: 'sortAttrs',
                      params: {
                        xmlnsOrder: 'alphabetical',
                      },
                    },
                  ],
                },
              ],
            ],
          },
        },
      }),
    ],
  },
  // webpack 解析模块加载选项
  resolve: {
    // 自动补全文件扩展名
    extensions: ['.vue', '.js', '.json'],
    // 配置路径别名
    alias: {
      '@': path.resolve(__dirname, '../src'),
    },
  },
  devServer: {
    port: 3000,
    open: true,
    // 解决vue-router刷新404问题
    historyApiFallback: true,
  },
  // 关闭性能分析，提升打包速度
  performance: false,
};
